import logging
from multiprocessing import Process, Queue
from pathlib import Path
from time import sleep
from timeit import default_timer as timer

import cv2


class Streamer:
    @staticmethod
    def rtsp_reader(url, child_sender):
        cap = cv2.VideoCapture(url)
        while True:
            if child_sender.full():
                cap.grab()
                continue
            ret, frame = cap.read()
            if not ret:
                logging.error(
                    "Failed to read frame from rtsp stream! "
                    "Subprocess will be terminated."
                )
                return False
            child_sender.put(frame)

    def __init__(self):
        self.stream_url = None
        self.streamer = None
        self.frame_interval = 0
        self.stream_type = None
        self.total_frame = 0
        self.last_save_time = 0
        self.save_img = False
        self.subprocess = None
        self.child_sender = None

    def __del__(self):
        if self.subprocess is not None:
            self.subprocess.terminate()
            self.subprocess.join()

    def start_rtsp_stream(self):
        self.child_sender = Queue(3)
        self.subprocess = Process(
            target=Streamer.rtsp_reader,
            args=(self.stream_url, self.child_sender),
        )
        self.subprocess.start()

    def end_rtsp_stream(self):
        logging.warning("Ending rtsp stream feting process...")
        self.subprocess.terminate()
        self.subprocess.join()
        self.subprocess = None

    def set_stream(self, stream_path):
        logging.info("Setting stream...")
        if self.subprocess is not None:
            self.end_rtsp_stream()

        self.stream_url = stream_path
        self.streamer = cv2.VideoCapture(stream_path)
        if self.stream_url.startswith("rtsp"):
            self.stream_type = "rtsp"
            self.start_rtsp_stream()
            logging.debug("Stream subprocess started.")

        elif Path(self.stream_url).is_file():
            self.stream_type = "file"
            self.frame_interval = 1 / self.streamer.get(cv2.CAP_PROP_FPS)
            self.total_frame = self.streamer.get(cv2.CAP_PROP_FRAME_COUNT)

        else:
            logging.error("Stream type is not supported!")

    def check_stream(self):
        if self.streamer is None:
            return False
        if not self.streamer.isOpened():
            return False
        return True

    def get_frame_interval(self):
        if self.stream_type == "rtsp":
            return 0
        if self.stream_type == "file":
            return self.frame_interval

    def get_frame(self):
        if self.stream_type == "file":
            if self.streamer.get(cv2.CAP_PROP_POS_FRAMES) == self.total_frame:
                logging.warning("Video Detect Finished!")
                return False, None
            res, frame = self.streamer.read()
            if not res:
                logging.error("Stream is not opened, please check the stream path!")
                return False, None

        elif self.stream_type == "rtsp":
            if self.subprocess is None:
                logging.error("Subprocess is not set!")
                return False, None
            if not self.subprocess.is_alive():
                logging.error(
                    "Rtsp fetching process is not alive, "
                    "please check the stream path!"
                )
                return False, None
            if self.child_sender.empty():
                start_time = timer()
                logging.warning(
                    "Frame queue is empty, Waiting for frame, max wait time: 5s"
                )
                while self.child_sender.empty():
                    if timer() - start_time > 5:
                        logging.error(
                            "Could't get frame, check the stream path then try again!"
                        )
                        return False, None
                    sleep(0.02)
                logging.warning(f"Frame got, wait time: {timer() - start_time:.3}s")
            frame = self.child_sender.get()
        else:
            logging.error("Stream url is not set!")
            return False, None
        return True, frame
